local Packet = import(".Packet")

local TCPConnecter = class("TCPConnecter")

TCPConnecter.EVENT_OPEN = "WS_EVENT_OPEN"
--TCPConnecter.EVENT_MESSAGE = "WS_EVENT_MESSAGE"
TCPConnecter.EVENT_CLOSE = "WS_EVENT_CLOSE"
TCPConnecter.EVENT_ERROR = "WS_EVENT_ERROR"

--The interval to send keepalive packet to server
local KeepaliveInterval = 15

--The amount of time not reviced data from server
local KeepaliveTimeout = 60


local DefaultConnectTimeout = 3


local sendBinaryFunc = cc.TcpSocket.send

local errorCodeDict = {
    [51] = L10N.getString("lua_code_text_427"),
    [60] = L10N.getString("lua_code_text_4"),
    [61] = L10N.getString("lua_code_text_494"),
    [89] = L10N.getString("lua_code_text_4"),
    [1001] = L10N.getString("lua_code_text_376"),
    [1002] = L10N.getString("lua_code_text_376"), -- 域名解析超时
}

-- dns 解析缓存
local DnsCacheDict = {}
local DnsCacheTimeout = 120

local ConnectState_Closed = 1
local ConnectState_Connecting = 2
local ConnectState_Connected = 3
local ConnectState_Closing = 4


function TCPConnecter:ctor()
    self._packetIndex = 0
    self._packages = {}
    self._handlerMap = {}
    self.keepalive = false
    self.tag = ""
    self._isWeakNetwork = false
    self._state = ConnectState_Closed
    self._delayTimeMs=0
    self._lastPingTime=0
    self.pingInterval=3
    self.weakThreshold=800
    self.delayWeakThreshold=1000
    self:resetStatus()
end

function TCPConnecter:log(...)
    release_print("->TCPConnecter",self.tag,...)
end

function TCPConnecter:open(host,port,cb,timeout,weakMS)
    if self._state==ConnectState_Connecting then
        self:log("is connecting can't open again")
        if cb then
            cb(-1,L("network_loading"))
        end
        return 
    end
    self._state = ConnectState_Connecting

    self.weakThreshold = weakMS
    self.delayWeakThreshold = weakMS + 200
    self:log("connecting",host,port)

    self.forceClosing = false
    self._startOpenTime = app:getTime()

    if not self._socket then
        self._socket = cc.TcpSocket:create()
    end

    self._socket:setEventsCallback(function(name,arg1,arg2)
            if name=="connected" then
                self._state = ConnectState_Connected
                self:log("connected useTime:",(app:getTime()-self._startOpenTime)*1000,"ms")
                self:onOpen_(arg1)
            elseif name =="message" then
                self:onMessage_(arg1,arg2)
            elseif name=="disconnected" then
                if arg1 == "" then arg1 = nil end
                self._socket:setEventsCallback(nil)
                self._state = ConnectState_Closed
                self:log("disconnected","isForce",self.forceClosing, arg1)
                self:onClose_(arg1)

                -- 强制关闭 or 错误为空 =》主动断开
                local enumKey
                if self.forceClosing or arg1 == nil then
                    enumKey = "BreakActive"
                else
                    enumKey = "BreakPassive"
                end
                if self.tag == "Battle" then
                    enumKey = "Battle" .. enumKey
                end
                EM:notify("OnNetworkStatusChange", Enums.NetworkStatusAdEvent[enumKey])

                -- 主动关闭的回调
                if self._closeCallback and self.forceClosing then
                    self._closeCallback(arg1)
                    self._closeCallback=nil
                end
            end
        end)

    self._socket:setKeepAliveTime(KeepaliveTimeout,KeepaliveInterval)

    local httpDNSCache = nil

    local function connect(connectHost)
        self:log("connecting after dns",connectHost,port)
        self._socket:connect(connectHost,port,function(code, err, ip)
                self:log("connect code:", code, "err:", err, "ip:", ip)

                -- 如果是ipV6连接失败，而ipV4存在，则再尝试使用ipV4连接
                local isNeedRetry = false
                if code ~= 0 and httpDNSCache and
                    (httpDNSCache[2] == connectHost) and
                    httpDNSCache[1] and httpDNSCache[1] ~= "0" then
                    isNeedRetry = true
                end

                if code == 0 then
                    self._state = ConnectState_Connected

                    -- 成功缓存ip
                    if ip ~= host and ip ~= "" then
                        DnsCacheDict[host] = {time=app:getTime(), ip=ip}
                        -- dump(DnsCacheDict, "DnsCacheDict update")
                    end

                    -- 获取连接状态，wifi/4G
                    if app:isMobilePlatform() then
                        self._connectionStatus = network.getInternetConnectionStatus()
                        self:log("connectionStatus start", self._connectionStatus)
                    end
                elseif not isNeedRetry then
                    if self.tag == "Session" then
                        EM:notify("OnNetworkStatusChange", Enums.NetworkStatusAdEvent.ConnFail)
                    else
                        EM:notify("OnNetworkStatusChange", Enums.NetworkStatusAdEvent.BattleConnFail)
                    end

                    self._state = ConnectState_Closed

                    if ip == "" then
                        -- 解析dns失败
                    else
                        if ip ~= host then
                            -- 链接失败，移除超时的缓存
                            local cache = DnsCacheDict[host]
                            if cache and (app:getTime() - cache.time) >= DnsCacheTimeout then
                                DnsCacheDict[host] = nil
                            end
                            -- dump(DnsCacheDict, "DnsCacheDict remove")
                        end
                    end
                end
                
                if isNeedRetry then
                    connect(httpDNSCache[1])
                    httpDNSCache = nil
                else
                    if cb then
                        local errmsg = errorCodeDict[code]
                        if code ~= 0 then
                            if network.isInternetConnectionAvailable() then
                                errmsg = errmsg or L("network_error")
                            else
                                -- 网络不可用统一使用提示
                                errmsg = L("lua_code_text_436")
                            end
                            errmsg = errmsg .. string.format("(%s)", code)
                        else
                            errmsg = errmsg or err
                        end
                        cb(code, errmsg)
                    end
                end
            end,timeout or DefaultConnectTimeout)
    end

    local function useLocalDNS()
        local hostCache = host
        if DnsCacheDict[host] then
            hostCache = DnsCacheDict[host].ip
            self:log("use local dns host", host, hostCache)
        end
        connect(hostCache)
    end

    -- 非IP要进行dns解析
    if not string.matchIPV4(host) then
        if Config.useHttpDNS then
            -- 使用dns服务
            app.channelAPI:getHostByNameAsync(host, function(ips)
                if ips then
                    -- ips=[ipV4, ipV6], tencent ios会解析出ipV6, android只有ipV4
                    local ipV4 = ips[1]
                    local ipV6 = ips[2]
                    if ipV6 and ipV6 ~= "0" then
                        httpDNSCache = ips
                        connect(ipV6)
                    elseif ipV4 and ipV4 ~= "0" then
                        connect(ipV4)
                    else
                        -- ipV4和ipV6都不可用，使用本地缓存
                        useLocalDNS()
                    end
                else
                    -- 失败使用本地缓存
                    useLocalDNS()
                end
            end)
        else
            -- 使用本地缓存
            useLocalDNS()
        end
    else
        connect(host)
    end
end

function TCPConnecter:close(cb, isForce)
    if isForce ~= nil then
        self.forceClosing = isForce
    else
        self.forceClosing = true
    end
    if self:isOpen() then
        self._state = ConnectState_Closing
        self._closeCallback = cb
        self._socket:disconnect()
    else
        if cb then
            cb()
        end
    end
end

function TCPConnecter:send(packetOrMessage,data,cb,node)
    if not self:isOpen() then 
        print(">TCPConnecter  send: socket not ready",self.tag)
        return 
    end

    local packet = nil
    if iskindof(packetOrMessage,"Packet") then
        packet = packetOrMessage
    else
        packet = Packet.create(packetOrMessage,data,cb)
    end

    local isCSMode = (cb and true or false)
    if isCSMode then
        self._packetIndex = math.loop(self._packetIndex + 1,1,65536)
        packet.id = self._packetIndex
    end
    
    packet.node = node

    if packet.id ~= 0 then
        packet.timeStamp = app:getTime()
        self._packages[packet.id] = packet
    else
        Packet.free(packet)
    end

    local encode = packet:encode()
    local command = assert(packet.command)
    sendBinaryFunc(self._socket,encode,command)
    -- self:log("send id: "..packet.id.." message=>"..packet.type.." command=>"..command.." len==>"..string.len(encode))
    return packet.id
end

function TCPConnecter:removeRequestCBById(id)
    local package = self._packages[id]
    if package then
        package.callbackFun=nil
    end
end

function TCPConnecter:removeRequestCBByName(name)
    for _,package in pairs(self._packages) do
        if package.type==name then
            package.callbackFun=nil
        end
    end
end

function TCPConnecter:resetStatus()
    self._delayTimeMs = 0
    self._isPing = false
    self._lastPingTime = 0
    self:_setWeakNetwork(false)
    self:_clearPingCron()
end


function TCPConnecter:notify(packetOrMessage,data)
    self:send(packetOrMessage,data,nil)
end

function TCPConnecter:on(message,cb)
    local handlers = self._handlerMap[message] or {}
    table.insert(handlers,cb)
    self._handlerMap[message] = handlers
end

function TCPConnecter:off(message)
    if message then
        self._handlerMap[message] = nil
    else
        self._handlerMap = {}
    end
end

function TCPConnecter:getDelayTimeMs()
    return self._delayTimeMs
end

function TCPConnecter:isConnecting()
    return self._state == ConnectState_Connecting
end

function TCPConnecter:isOpen()
    return self._state == ConnectState_Connected
end

function TCPConnecter:isClosed()
    return self._state == ConnectState_Closed
end

function TCPConnecter:isClosing()
    return self._state == ConnectState_Closing
end

function TCPConnecter:invokeHandlers(message,...)
    local handlers = self._handlerMap[message]
    if handlers then
        for _,hl in pairs(handlers) do
            hl(...)
        end
    end
end

--private
function TCPConnecter:onOpen_(arg)
    self:resetStatus()
    self:invokeHandlers(TCPConnecter.EVENT_OPEN,arg)
    self.forceClosing = false
end

function TCPConnecter:onMessage_(msgId,data)
    local repPacket,rawData = Packet.createFromBinary(msgId,data)
    local packages = self._packages
    local reqPacket = packages[repPacket.id]

    -- self:log("revice id: "..(repPacket.id or "").." message=>"..repPacket.type.." len==>"..string.len(arg),os.date())
    local swallowEvent = false
    if reqPacket then 

        packages[repPacket.id] = nil

        -- local currentTime = app:getTime()

        -- local delayTime =  currentTime - reqPacket.timeStamp 
        -- self._delayTimeMs = math.ceil(delayTime*1000)

        -- self._lastAciveTime = currentTime

        local err = nil
        local msg = repPacket.data
        if repPacket.type=="shared.ServerMessage" then
            err = repPacket.data
            msg = nil
        end
        
        local needInvoke = true
        local node = reqPacket.node
        if node and cc.isDead(node) then
            needInvoke = false
        end
        if needInvoke then
            swallowEvent = reqPacket:invoke(msg, err)
        end

        Packet.free(reqPacket)
    end

    if not swallowEvent then
        self:invokeHandlers(repPacket.type,repPacket.data, rawData)
    end

    Packet.free(repPacket)
end

function TCPConnecter:onClose_(arg)
    self:resetStatus()
    self._pingFunc = nil
    for _,v in pairs(self._packages) do
        Packet.free(v)
    end
    self._packages={}
    self:invokeHandlers(TCPConnecter.EVENT_CLOSE,{forceClosing=self.forceClosing,error=arg})
end

function TCPConnecter:isWeakNetwork()
    return self._isWeakNetwork
end

--任何一个sequece包都可以当做ping/pong协议,有业务决定用哪个消息
function TCPConnecter:setPingFunc(func)
    self._pingFunc = func
end

-- 清理ping超时任务
function TCPConnecter:_clearPingCron()
    if self._pingCron then
        scheduler.cancelCron(self._pingCron)
        self._pingCron = nil
    end
end

function TCPConnecter:tick(dt)
    if not TCP_TICK_ENABLE then return end

    if self._state ~= ConnectState_Connected then return end

    local pingFunc = self._pingFunc
    if not pingFunc then return end

    local delayTime = self._delayTimeMs

    if not self._isPing then
        local currentTime = app:getTime()
        local passedTime = currentTime - self._lastPingTime
        if passedTime > self.pingInterval then
            -- print("Banson -- delayTime: " .. delayTime .. " ,tag:" .. self.tag)

            self._isPing = true
            self._lastPingTime = currentTime

            pingFunc(self,function()
                self:_clearPingCron()

                local getPingTime = app:getTime()

                local delayTime =  math.ceil((getPingTime - currentTime)*1000)
                self._delayTimeMs = delayTime

                self:_setWeakNetwork(delayTime > self.weakThreshold)

                self._isPing = false
            end)

            self:_clearPingCron()
            self._pingCron = scheduler.newCron(function()
                    self._pingCron = nil
                    self:_setWeakNetwork(true)
                end, self.delayWeakThreshold/1000)

            if app:isMobilePlatform() then
                if self._connectionStatus ~= network.getInternetConnectionStatus() then
                    if self.tag == "Session" then
                        self:log("connectionStatus change", self._connectionStatus, network.getInternetConnectionStatus())
                        release_print("onSwitchNetwork", self.tag)
                        EM:notify("onSwitchNetwork",self.tag)
                        self:_setWeakNetwork(true)
                        self:close(nil, true)
                    end
                end
            end
        end
    end
end

function TCPConnecter:_setWeakNetwork(isWeakNetwork)
    -- print("TCPConnecter:_setWeakNetwork", isWeakNetwork, self._isWeakNetwork)
    if self._isWeakNetwork ~= isWeakNetwork then
        self._isWeakNetwork = isWeakNetwork
        EM:notify("onNetworkWeak", isWeakNetwork, self.tag)
    end
end


return TCPConnecter